import fs from "fs";
import { StructValueType as SVT } from "k-coder";
import path from "path";

/**
 * 协议代码构建
 */
export class KProtoBuilder {

    /** 协议id字典 */
    private protoIdDic: { [protoId: string]: boolean } = {};
    /** 所有协议结构定义 */
    private allProtoDefs: IProtoDef[] = [];

    /** 协议定义 */
    private protos: {
        /** 文件名 */
        fileName: string,
        /** 类型结构定义 */
        typeDefs: ITypeStructDef[],
        /** 协议结构定义 */
        protoDefs: IProtoDef[]
    }[] = [];

    constructor(
        /** 协议目录 */
        readonly inputDir: string,
        /** 输出目录 */
        readonly outputDir: string,
        /** 是否输出路由配置 */
        readonly outProtoCfg: boolean,
        /** 输出代码文件名 */
        readonly protoCodeName = "KProto",
        /** 输出协议结构代码文件名 */
        readonly structCodeName = "KProtoStruct",
        /** 输出协议路由代码文件名 */
        readonly routeCodeName = "KProtoCfg",
        /** 输出协议id名 */
        readonly protoIdName = "KProtoId",
        /** 输出协议定义前缀 */
        readonly prefix = "kp",
    ) { }

    /** 构建协议  */
    build(onDone?: (time: number, fileCount: number, protoCount: number, typeCount: number) => void) {
        const now = Date.now();
        this.loadAllProto(() => {
            this.sort();
            this.buildProtoCode();
            this.buildStructCode();
            this.outProtoCfg && this.buildRouteCode();
            if (onDone) {
                let protoCount = 0;
                let typeCount = 0;
                this.protos.forEach(proto => {
                    protoCount += proto.protoDefs.length;
                    typeCount += proto.protoDefs.length;
                });
                onDone(Date.now() - now, this.protos.length, protoCount, typeCount);
            }
        });
    }

    /** 读取所有协议 */
    private loadAllProto(onDone: () => void) {
        //遍历协议目录读取协议
        console.log("===> 读取协议");
        const now = Date.now();
        const files = fs.readdirSync(this.inputDir);
        let count = files.length;
        files.forEach(fileName => {
            const filePath = path.resolve(this.inputDir, fileName);
            const match = fileName.match(/(.*)\.kproto$/);
            if (match) {
                console.log(filePath);
                //检查命名是否规范
                assert(!match[1].match(/[^_0-9a-zA-Z]/), `[${fileName}.kproto]命名不规范`);
                fs.readFile(filePath, "utf-8", (err, data) => {
                    if (err) {
                        console.log(`${fileName}读取失败！`);
                        fileDone();
                        return;
                    }
                    this.loadProto(data, match[1]);
                    fileDone();
                });
            }
        });

        function fileDone() {
            if (--count === 0) {
                console.log(`耗时：${Date.now() - now}ms`);
                onDone();
            }
        }
    }

    /** 读取单个协议 */
    private loadProto(data: string, fileName: string) {

        const that = this;
        const typeDefs: ITypeStructDef[] = [];
        const protoDefs: IProtoDef[] = [];
        this.protos.push({ fileName, typeDefs, protoDefs });

        //忽略#号后内容
        data = data.replace(/#.*/g, "");
        let def: IStructDef;
        let typeDef: ITypeStructDef;
        let protoDef: IProtoDef;
        let note: string[] = [];
        let fieldIdx = 0;
        const nameDic: any = {};

        //遍历每行读取定义
        data.split(/\r?\n/).forEach((ln, lnIdx) => {
            if (!ln) return;

            let match = ln.match(/^\s*\/\/(.*)/);
            if (match) {
                //匹配到注释
                note.push(match[1]);
                return;
            }
            if (!typeDef && !protoDef) {
                //===匹配定义
                match = ln.match(/^\s*(type|\d+_\d+(?::[_0-9a-zA-Z\.]+)?) +([_0-9a-zA-Z]+) *{/);
                if (match) {
                    //匹配到定义
                    const name = match[2];
                    assert(!nameDic[name], errMsg("名称定义重复"));
                    nameDic[name] = true;
                    if (match[1] === "type") {
                        assert(match[3] != ";", errMsg("类型结构定义不能为空"));
                        //通用结构定义
                        fieldIdx = 0;
                        typeDefs.push(def = typeDef = {
                            fileName,
                            name: name,
                            note,
                            fields: [],
                            struct: "[]",
                        } as any);
                    } else {
                        //协议结构定义
                        const [protoId, router] = match[1].split(":");
                        assert(!this.protoIdDic[protoId], errMsg("协议号重复"));
                        protoDef = {
                            protoId,
                            name,
                            fileName,
                            router: router ? `${router}.${name}` : "",
                            note
                        }
                        this.protoIdDic[protoId] = true;
                        this.allProtoDefs.push(protoDef);
                        protoDefs.push(protoDef);
                    }
                } else {
                    checkOther(ln);
                }
            } else {
                //===读取定义

                /** 匹配字段 */
                function matchField(fields: IFieldDef[]) {
                    match = ln.match(fieldReg);
                    if (match) {
                        //匹配到字段
                        fields.push(parseField(match));
                        return true;
                    }
                    return false;
                }

                /** 匹配结束 */
                function matchEnd() {
                    if (ln.match(/^\s*\}/)) {
                        return true;
                    } else {
                        checkOther(ln);
                    }
                    return false;
                }

                if (typeDef) {
                    //===读取类型结构定义
                    if (!matchField(typeDef.fields)) {
                        if (matchEnd()) {
                            typeDef.struct = buildFieldsStruct(typeDef.fields);
                            assert(typeDef.struct, "类型结构定义不能为空");
                            def = typeDef = null;
                        }
                    }
                } else if (protoDef) {
                    //===读取协议结构定义
                    if (!def) {
                        //匹配tos/toc结构
                        match = ln.match(/^\s*(tos|toc) *({|;)/);
                        if (match) {
                            fieldIdx = 0;
                            def = {
                                note: note.length > 0 ? note : protoDef.note,
                                name: `${protoDef.name}_${match[1]}`,
                                fields: [],
                                struct: ""
                            }
                            if (match[1] === "tos") {
                                protoDef.tosStruct = def;
                            } else {
                                protoDef.tocStruct = def;
                            }
                            if (match[2] === ";") {
                                //没有结构
                                def = null;
                            }
                        } else if (matchEnd()) {
                            protoDef = null;
                        }
                    } else {
                        //===读取tos/toc结构定义
                        if (!matchField(def.fields)) {
                            if (matchEnd()) {
                                def.struct = buildFieldsStruct(def.fields);
                                def = null;
                            }
                        }
                    }
                }

            }
            note = [];

            function errMsg(msg: string) {
                return `(行:${lnIdx + 1})${ln} >>> ${msg}`
            }

            function checkOther(ln: string) {
                assert(!/\S/.test(ln), errMsg("错误定义"))
            }

            /** 解析字段 */
            function parseField(match: RegExpMatchArray): IFieldDef {
                const fieldName = match[1];
                /** 是否使用类型结构 */
                const isType = match[2] === "@";
                let type = match[3];
                let svt: SVT = (SVT as any)[type];
                switch (type) {
                    case "uint": svt = SVT.auint32; break;
                    case "int": svt = SVT.aint32; break;
                    case "floatUint": svt = SVT.floatAuint32; break;
                    case "floatInt": svt = SVT.floatAint32; break;
                }
                /** 特殊浮点型参数 */
                const floatParam = match[4] ? parseInt(match[4]) : 0;
                /** 数组维度 */
                const arrStr = match[5] || "";
                const arrVec = Math.floor(arrStr.length / 2);

                let comSt = "";
                let tsType: string;
                const struct: any[] = [`"${fieldName}"`];

                switch (svt) {
                    case SVT.bool:
                        tsType = "boolean";
                        struct.push(svt);
                        break;
                    case SVT.byte:
                    case SVT.uint8:
                    case SVT.uint16:
                    case SVT.uint32:
                    case SVT.uint64:
                    case SVT.auint32:
                    case SVT.auint64:
                    case SVT.int8:
                    case SVT.int16:
                    case SVT.int32:
                    case SVT.int64:
                    case SVT.aint32:
                    case SVT.aint64:
                    case SVT.float32:
                    case SVT.floatAuint32:
                    case SVT.floatAuint64:
                    case SVT.floatAint32:
                    case SVT.floatAint64:
                    case SVT.floatStr:
                        tsType = "number"
                        struct.push(svt);
                        break;
                    case SVT.bytes:
                        tsType = "number[]"
                        struct.push(svt);
                        break;
                    case SVT.str:
                        tsType = "string"
                        struct.push(svt);
                        break;
                    case SVT.any:
                        tsType = "any"
                        struct.push(svt);
                        break;
                    case SVT.obj:
                        tsType = "{ [key: string]: any }"
                        struct.push(svt);
                        break;
                    case SVT.arr:
                        tsType = "any[]"
                        struct.push(svt);
                        break;
                    default:
                        if (isType) {
                            //类型结构
                            const split = type.split(".");
                            tsType = `${that.prefix}_${split.length > 1 ? `${split[0]}_${split[1]}` : `${fileName}_${type}`}`;
                            type = split.length > 1 ? type : `${fileName}.${type}`;
                            if (typeDef) {
                                typeDef.deps ||= [];
                                typeDef.deps.push(fieldIdx, `"${type}"`);
                            } else {
                                comSt = `_["${type}"]`;
                            }
                            struct.push(SVT.struct);
                        } else {
                            assert(false, errMsg("字段解析失败"));
                        }
                        break;
                }
                //数组维度
                tsType += arrStr;
                struct.push(arrVec);
                //特殊参数
                floatParam && struct.push(floatParam);
                comSt && struct.push(comSt);
                !arrVec && struct.length === 3 && struct.pop();
                ++fieldIdx;

                return {
                    lnIdx,
                    note,
                    name: fieldName,
                    tsType,
                    struct: `[${struct.join(", ")}]`,
                }
            }
        });

        /** 构建字段结构 */
        function buildFieldsStruct(fields: IFieldDef[]) {
            if (fields.length) {
                let structs: any[] = [];
                fields.forEach(field => structs.push(field.struct));
                return `[${structs.join(", ")}]`;
            } else {
                return "";
            }
        }
    }

    /** 排序 */
    private sort() {
        this.allProtoDefs.sort((a, b) => a.protoId.localeCompare(b.protoId));
        this.protos.sort((a, b) => a.fileName > b.fileName ? 1 : a.fileName < b.fileName ? - 1 : 0);
    }

    /** 构建协议代码 */
    private buildProtoCode() {
        console.log("===> 构建协议代码");
        const now = Date.now();

        let code = "";
        code += `/**\n`;
        code += ` * k-proto协议\n`;
        code += ` * (工具生成，请不要手动修改...)\n`;
        code += ` */\n`;
        code += `export namespace ${this.protoCodeName} {\n`;
        code += `\n`;
        code += `    let b: any, Coder: any;\n`;
        code += `    /** 传入k-coder来进行初始化 */\n`;
        code += `    export function init(kcoder: any) {\n`;
        code += `        b = new kcoder.KBinary();\n`;
        code += `        Coder = kcoder.StructCoder;\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    const idBufDic: any = {}, tosCoderDic: any = {}, tocCoderDic: any = {};\n`;
        code += `\n`;
        code += `    /** 加载单个协议结构 */\n`;
        code += `    export function load(protoId: any, struct: any) {\n`;
        code += `        let st: any;\n`;
        code += `        tosCoderDic[protoId] = (st = struct[protoId][0]) ? new Coder(st) : undefined;\n`;
        code += `        tocCoderDic[protoId] = (st = struct[protoId][1]) ? new Coder(st) : undefined;\n`;
        code += `        if (!idBufDic[protoId]) {\n`;
        code += `            const id = protoId.split("_");\n`;
        code += `            idBufDic[protoId] = b.wStart().wAuint32(id[0]).wAuint32(id[1]).wEnd();\n`;
        code += `        }\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 加载所有协议结构 */\n`;
        code += `    export function loadAll(struct: any, onCount?: (count: number) => void) {\n`;
        code += `        let c = 0;\n`;
        code += `        for (let protoId in struct) {\n`;
        code += `            load(protoId, struct);\n`;
        code += `            if (onCount) {\n`;
        code += `                onCount(++c);\n`;
        code += `            }\n`;
        code += `        }\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 编码协议id */\n`;
        code += `    export function encodeId(id: ${this.protoIdName}) {\n`;
        code += `        b.wStart().wBuf(idBufDic[id]);\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 编码tos协议 */\n`;
        code += `    export function encodeTos<ID extends ${this.protoCodeName}TosNeed>(id: ID, data: ${this.protoCodeName}Tos[ID]): Uint8Array\n`;
        code += `    export function encodeTos<ID extends ${this.protoCodeName}TosNever>(id: ID): Uint8Array\n`;
        code += `    export function encodeTos(id: ${this.protoIdName}, data?: any): Uint8Array {\n`;
        code += `        tosCoderDic[id]?.encode(data, b);\n`;
        code += `        return b.wEnd();\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 编码toc协议 */\n`;
        code += `    export function encodeToc<ID extends ${this.protoCodeName}TocNeed>(id: ID, data: ${this.protoCodeName}Toc[ID]): Uint8Array\n`;
        code += `    export function encodeToc<ID extends ${this.protoCodeName}TocNever>(id: ID): Uint8Array\n`;
        code += `    export function encodeToc(id: ${this.protoIdName}, data?: any): Uint8Array {\n`;
        code += `        tocCoderDic[id]?.encode(data, b);\n`;
        code += `        return b.wEnd();\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 解码协议id */\n`;
        code += `    export function decodeId(buf: Uint8Array) {\n`;
        code += `        b.rStart(buf);\n`;
        code += `        return \`\${b.rAuint32()}_\${b.rAuint32()}\`;\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 解码tos协议 */\n`;
        code += `    export function decodeTos<ID extends ${this.protoCodeName}TosNeed>(id: ID, buf?: Uint8Array): ${this.protoCodeName}Tos[ID]\n`;
        code += `    export function decodeTos<ID extends ${this.protoCodeName}TosNever>(id: ID, buf?: Uint8Array): void\n`;
        code += `    export function decodeTos(id: ${this.protoIdName}, buf?: Uint8Array) {\n`;
        code += `        return tosCoderDic[id]?.decode(buf || b);\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 解码toc协议 */\n`;
        code += `    export function decodeToc<ID extends ${this.protoCodeName}TocNeed>(id: ID, buf?: Uint8Array): ${this.protoCodeName}Toc[ID]\n`;
        code += `    export function decodeToc<ID extends ${this.protoCodeName}TocNever>(id: ID, buf?: Uint8Array): void\n`;
        code += `    export function decodeToc(id: ${this.protoIdName}, buf?: Uint8Array) {\n`;
        code += `        return tocCoderDic[id]?.decode(buf || b);\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    /** 解码协议id后剩下的数据buf */\n`;
        code += `    export function getDataBuf() {\n`;
        code += `        return b.rCut(b.rBuf.length - b.rPos);\n`;
        code += `    }\n`;
        code += `\n`;
        code += `    const win: any = globalThis || window;\n`;
        code += `    win["${this.protoIdName}"] = { ${this.allProtoDefs.map(def => `${def.name}: "${def.protoId}"`).join(", ")} };\n`;
        code += `\n`;
        code += `}\n`;

        code += `\n`;
        code += `declare global {\n`;
        code += `\n`;
        code += "    /** 协议号 */\n";
        code += `    enum ${this.protoIdName} {\n`;
        for (const def of this.allProtoDefs) {
            buildNote(def.note, "        ");
            code += `        ${def.name} = "${def.protoId}",\n`;
        }
        code += "    }\n";
        code += "\n";

        code += `    /** 发往服务端的协议 */\n`;
        code += `    interface ${this.protoCodeName}Tos {\n`;
        for (const def of this.allProtoDefs) {
            if (!def.tosStruct) continue;
            code += `        [${this.protoIdName}.${def.name}]: ${def.tosStruct.fields.length > 0 ? `${this.prefix}_${def.fileName}_${def.name}_tos` : "never"}\n`;
        }
        code += `    }\n`;
        code += `\n`;
        code += `    type ${this.protoCodeName}TosNeed = keyof Pick<${this.protoCodeName}Tos, { [key in keyof ${this.protoCodeName}Tos]: ${this.protoCodeName}Tos[key] extends never ? never : key }[keyof ${this.protoCodeName}Tos]>;\n`
        code += `    type ${this.protoCodeName}TosNever = Exclude<keyof ${this.protoCodeName}Tos, ${this.protoCodeName}TosNeed>;\n`
        code += `\n`;
        code += `    /** 发往客户端的协议 */\n`;
        code += `    interface ${this.protoCodeName}Toc {\n`;
        for (const def of this.allProtoDefs) {
            if (!def.tocStruct || !def.tocStruct.fields.length) continue;
            code += `        [${this.protoIdName}.${def.name}]: ${this.prefix}_${def.fileName}_${def.name}_toc\n`;
        }
        code += `    }\n`;
        code += `\n`;
        code += `    type ${this.protoCodeName}TocNeed = keyof Pick<${this.protoCodeName}Toc, { [key in keyof ${this.protoCodeName}Toc]: ${this.protoCodeName}Toc[key] extends never ? never : key }[keyof ${this.protoCodeName}Toc]>;\n`
        code += `    type ${this.protoCodeName}TocNever = Exclude<keyof ${this.protoCodeName}Toc, ${this.protoCodeName}TocNeed>;\n`
        code += `\n`;
        //写入协议结构
        for (const proto of this.protos) {
            const defs: IStructDef[] = [];
            for (const def of proto.typeDefs) {
                defs.push(def);
            }
            for (const def of proto.protoDefs) {
                def.tocStruct && def.tocStruct.fields.length && defs.push(def.tocStruct);
                def.tosStruct && def.tosStruct.fields.length && defs.push(def.tosStruct);
            }

            for (const def of defs) {
                buildNote(def.note, "    ");
                code += `    interface ${this.prefix}_${proto.fileName}_${def.name} {\n`;
                def.fields.forEach(field => {
                    buildNote(field.note, "        ");
                    code += `        ${field.name}: ${field.tsType},\n`;
                });
                code += `    }\n\n`;
            }
        }
        code += `}`;

        fs.writeFileSync(path.resolve(this.outputDir, `${this.protoCodeName}.ts`), code, { encoding: "utf-8", flag: "w+" });

        /** 构建注释 tab：缩进*/
        function buildNote(note: string[], tab: string) {
            if (!note || !note.length) return;
            if (note.length === 1) {
                //单行注释
                code += `${tab}/** ${note} */\n`;
            } else {
                //多行注释
                code += `${tab}/**\n`;
                note.forEach(line => code += `${tab} * ${line}\n`);
                code += `${tab} */\n`;
            }
        }

        console.log(`耗时：${Date.now() - now}ms`);
    }

    buildStructCode() {
        console.log("===> 构建结构代码");
        const now = Date.now();

        let code = "//工具生成，请不要手动修改...\n";
        //类型结构
        code += `function $(key: string, st: any, ...deps: any[]) {\n`;
        code += `    deps.length && depFns.push(() => {\n`;
        code += `        for (let i = 0; i < deps.length; ++i) {\n`;
        code += `            _[key][deps[i]][3] = _[deps[++i]];\n`;
        code += `        }\n`;
        code += `    });\n`;
        code += `    Object.defineProperty(_, key, { get: () => Object.defineProperty(_, key, { value: st })[key], enumerable: true, configurable: true })[key];\n`;
        code += `}\n`;
        code += `const _: any = {}, depFns: (() => void)[] = [];\n`;
        for (const proto of this.protos) {
            for (const def of proto.typeDefs) {
                code += `$("${proto.fileName}.${def.name}", ${def.struct}${def.deps ? `, ${def.deps.join(", ")}` : ""});\n`;
            }
        }
        code += `depFns.forEach(fn => fn());\n\n`;

        code += `export const ${this.structCodeName}Count = ${this.allProtoDefs.length};\n`;
        code += `export default {\n`;
        for (const def of this.allProtoDefs) {
            code += `    "${def.protoId}": [${def.tosStruct ? def.tosStruct.struct : ""},${def.tocStruct ? ` ${def.tocStruct.struct}` : ""}],\n`;
        }
        code += `}`;

        fs.writeFileSync(path.resolve(this.outputDir, `${this.structCodeName}.ts`), code, { encoding: "utf-8", flag: "w+" });
        console.log(`耗时：${Date.now() - now}ms`);
    }

    buildRouteCode() {
        console.log("===> 构建结构代码");
        const now = Date.now();

        let code = "//工具生成，请不要手动修改...\n";
        code += `export default { \n`;
        for (let def of this.allProtoDefs) {
            if (!def.router) continue;
            code += `    "${def.protoId}": "${def.router}", \n`;
        }
        code += `}`;

        fs.writeFileSync(path.resolve(this.outputDir, `${this.routeCodeName}.ts`), code, { encoding: "utf-8", flag: "w+" });
        console.log(`耗时：${Date.now() - now}ms`);
    }
}

/** 字段正则 */
const fieldReg = /^\s*([_0-9a-zA-Z]+\??) *: *(?:(@?)([\._0-9a-zA-Z]+)(?:\((\d+)\))*((?:\[\])+)*)+ *;?/;

/** 断言失败退出程序并输出错误信息 */
function assert(cond: any, msg?: string) {
    if (!cond) {
        console.log(msg);
        process.exit();
    }
    return cond;
}

/** 字段定义 */
interface IFieldDef {
    lnIdx: number, note: string[], name: string, tsType: string, struct: string, dep?: any[]
}

/** 结构定义 */
interface IStructDef {
    note: string[], name: string, fields: IFieldDef[], struct: string
}

/** 类型结构定义 */
interface ITypeStructDef extends IStructDef {
    fileName: string, deps?: any[]
}

/** 协议结构定义 */
interface IProtoDef {
    /** 注释 */
    note: string[],
    /** 协议号 */
    protoId: string,
    /** 协议名 */
    name: string,
    /** 文件名 */
    fileName: string,
    /** 路由 */
    router: string,
    /** 发服务端结构 */
    tosStruct?: IStructDef,
    /** 发客户端结构 */
    tocStruct?: IStructDef
}